package com.taomus.mytools.lang.utils.asm;

import java.util.HashMap;
import java.util.Map;

import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.commons.GeneratorAdapter;
import org.objectweb.asm.commons.Method;
import org.objectweb.asm.util.TraceClassVisitor;

public class CreateFunction {

	private GeneratorAdapter ga;
	private Integer localStoreNumber = 0;
	private Map<String, Integer> localStoreVeriable = null;
	protected TraceClassVisitor tcvisitor = null;
	protected Map<String, Class<?>> fieldVeriable = null;
	protected String className;

	public CreateFunction() {
		super();
	}

	/**
	 * 创建函数 defaultScope
	 * 
	 * @param method
	 * @param opcodesScope
	 */
	public CreateFunction FuncBegin(String method, int defaultScope) {
		this.localStoreVeriable = new HashMap<>();
		ga = new GeneratorAdapter(defaultScope, Method.getMethod(method), null, null, tcvisitor);
		return this;
	}

	public CreateFunction FuncPublicBegin(String method) {
		return this.FuncBegin(method, Opcodes.ACC_PUBLIC);
	}

	public CreateFunction FuncStaticBegin(String method) {
		return this.FuncBegin(method, Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC);
	}

	public CreateFunction FuncPrivateBegin(String method) {
		return this.FuncBegin(method, Opcodes.ACC_PRIVATE);
	}

	public CreateFunction FuncProtectedBegin(String method) {
		return this.FuncBegin(method, Opcodes.ACC_PROTECTED);
	}

	public CreateFunction getLocalStore(String key, Class<?> zlass) {
		Integer index = localStoreVeriable.get(key);
		ga.loadLocal(index, Type.getType(zlass));
		return this;
	}

	public <T> CreateFunction setLocalStore(String key, Class<T> zlass, T val) {
		Integer index = ++(this.localStoreNumber);
		localStoreVeriable.put(key, index);
		storeValue(zlass, val);
		ga.storeLocal(index, Type.getType(zlass));
		return this;
	}
	
	public CreateFunction invoke(String method) {
		ga.loadThis();
		ga.invokeVirtual(Type.getType(this.className), Method.getMethod(method));
		return this;
	}

	public <T> CreateFunction argsToThis(int index, String thisName, Class<T> zlass) {
		ga.loadThis();
		ga.loadArg(index);
		if (this.fieldVeriable.containsKey(thisName) && this.fieldVeriable.get(thisName).equals(zlass)) {
			ga.putField(Type.getType(this.className), thisName, Type.getType(zlass));
		} else {
			tcvisitor.visitField(Opcodes.ACC_PROTECTED, thisName, Type.getType(zlass).getDescriptor(), null, null)
					.visitEnd();
			this.fieldVeriable.put(thisName, zlass);
			ga.putField(Type.getType(this.className), thisName, Type.getType(zlass));
		}
		return this;

	}

	public <T> CreateFunction setThis(String name, Class<T> zlass, T val) {
		ga.loadThis();
		storeValue(zlass, val);
		if (this.fieldVeriable.containsKey(name) && this.fieldVeriable.get(name).equals(zlass)) {
			ga.putField(Type.getType(this.className), name, Type.getType(zlass));
		} else {
			tcvisitor.visitField(Opcodes.ACC_PROTECTED, name, Type.getType(zlass).getDescriptor(), null, null)
					.visitEnd();
			this.fieldVeriable.put(name, zlass);
			ga.putField(Type.getType(this.className), name, Type.getType(zlass));
		}
		return this;
	}

	public CreateFunction getThis(String name) {
		ga.loadThis();
		Class<?> zlass = this.fieldVeriable.get(name);
		ga.getField(Type.getType(this.className), name, Type.getType(zlass));
		return this;
	}

	private <T> void storeValue(Class<T> zlass, T val) {
		if (zlass.equals(int.class)) {
			ga.push((Integer) val);
		} else if (zlass.equals(long.class)) {
			ga.push((Long) val);
		} else if (zlass.equals(char.class)) {
			ga.push((Character) val);
		} else if (zlass.equals(double.class)) {
			ga.push((Double) val);
		} else if (zlass.equals(float.class)) {
			ga.push((Float) val);
		} else if (zlass.equals(boolean.class)) {
			ga.push((Boolean) val);
		} else if (zlass.equals(String.class)) {
			ga.push((String) val);
		} 
	}

	public CreateFunction appendCode(ICode code) {
		code.code(ga);
		return this;
	}

	public void FuncEnd() {
		ga.returnValue();
		ga.endMethod();
	}

}